package com.kpetruk.tinystat;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;


/**
 * 
 * @author Konstantin Petrukhnov
 * @date 25.12.2010
 *
 */
public class Statistics {
  //map of counters, max value is 2 billions (2^31-1). 
  private HashMap<String, Integer> statsInt = new HashMap<String, Integer>(); 
  //map of lists
  private HashMap<String, String> statsList = new HashMap<String, String>(); 
  //always output at the end
  private ArrayList<String> criticalMessages = new ArrayList<String>();
  //<tagName,<counterName>>
  private HashMap <String, ArrayList<String>> counterTags = new HashMap<String, ArrayList<String>>();
  //<tagName,<listName>>
  private HashMap <String, ArrayList<String>> listTags = new HashMap<String, ArrayList<String>>();
  
  //output formating
  public static String newline = System.getProperty("line.separator");
  private String listHeader = "-= ListStatistics =-"+newline;
  private String crittHeader = "-= CRITICAL =-"+newline;
  private String statHeader = "---=== Statistics ===---"+newline;
  
  /**
   * For singleton usage.
   * @return singleton
   */
  public static Statistics getInstance() {
    return StatisticsLoader.INSTANCE;
  }
  
  /**
   * General usage (multiple instances).
   */
  public Statistics() {
    
  }
  
  /**
   * Add value to specified list
   * @param key
   * @param value
   */
  public void addToList(String key, String value){
    String storedVal = statsList.get(key);
    if (storedVal==null) {
      statsList.put(key, value);
    } else {
      statsList.put(key, storedVal+newline+value);
    }
    
  }
  
  /**
   * increment counter by specified amount. If counter is not exist, create it with value 1.
   * counter use Integer type to store value. So Integer limitations applies.
   * @param name
   * @param increment
   */
  public void increment(String name, int increment){
    if(!statsInt.containsKey(name)) {
      statsInt.put(name, increment);
    } else {
      statsInt.put(name, statsInt.get(name)+increment);
    }
  }
  
  /**
   * Increment counter by one. Call increment(name, 1)
   * @param name
   */
  public void increment(String name){
    increment(name, 1);
  }
  
  
  /**
   * Add mesage to critical list. If message already exist - don't add. 
   * @param msg
   */
  public void critical(String msg){
    //add only if it not contain
    if(!criticalMessages.contains(msg)){
      criticalMessages.add(msg);
    }
  }
  
  
  /**
   * Output all gathered data with "\n" formatting
   * @return
   */
  public String outAll(){
    String ret = statHeader;
    //counters
    for(String key : statsInt.keySet()) {
      ret += outSingle(key)+newline;
    }
    //lists
    if(statsList.size()>0){
      ret += listHeader;
      for(String key : statsList.keySet()) {
        ret += outList(key);
      }
    }
    
    //critical (always at the end)
    if(criticalMessages.size()>0){
      ret += crittHeader;
      for(String msg : criticalMessages) {
        ret += msg+newline;
      }
    }

    return ret;
  }
  
  /**
   * Output specified list.
   * @param key
   * @return
   */
  public String outList(String key) {
    if(!statsList.containsKey(key)){
      return null;
    }else{
      return "- "+key+" -"+newline+statsList.get(key)+newline;
    }
  }
  
  /**
   * Output specified counter. Format:
   *   name: value
   * @param name
   * @return String representation of counter. If counter not exist, will return counter with 0 value!
   */
  public String outSingle(String name) {
    return name + ": " +getIntValue(name);
  }
  
  /**
   * get value for specified counter.
   * @param name
   * @return
   */
  public int getIntValue(String name) {
    if(statsInt.containsKey(name)){
      return statsInt.get(name);
    }
    return 0;
  }
  
  /**
   * Add counterName to specified tag (if name exist - not add).
   * @param tag
   * @param counterName
   */
  public void addCounterTag (String tag, String counterName) {
    addTag(counterTags, tag, counterName);
  }
  
  /**
   * Add listName to specified tag (if name exist - not add).
   * @param tag
   * @param counterName
   */
  public void addListTag (String tag, String listName) {
    addTag(listTags, tag, listName);
  }
  
  /**
   * Underlying method for adding tags.
   * @param tags
   * @param tag
   * @param name
   */
  private void addTag( HashMap <String, ArrayList<String>> tags, String tag, String name) {
    ArrayList<String> storedTag = tags.get(tag);
    //create tag if not exist
    if (storedTag==null) {
      storedTag = new ArrayList<String>();
      tags.put(tag, storedTag);
    }
    //add counter/list to tag (only if it not exist)
    if (!storedTag.contains(name)) {
      storedTag.add(name);
    }
  }
  
  /**
   * output counters for specified tag.
   * @param tag
   * @return
   */
  public String outCounterTag (String tag) {
    String result = "- "+tag+" -";
    for (String counterName : counterTags.get(tag)) {
      result += newline+outSingle(counterName);
    }
    return result;
  }
  
  /**
   * Output lists for specified tag.
   * @param tag
   * @return
   */
  public String outListTag (String tag) {
    String result = "-= "+tag+" =-";
    for (String listName : listTags.get(tag)) {
      result += newline+outList(listName);
    }
    return result;
  }

  
  
  /**
   * For singleton instantiation 
   * @author Konstantin Petrukhnov
   *
   */
  private static class StatisticsLoader {
    private static final Statistics INSTANCE = new Statistics();
  }
}



